
/* Copyright (c) Mark J. Kilgard, 1994.  */

/* This program is freely distributable without licensing fees
   and is provided without guarantee or warrantee expressed or
   implied. This program is -not- in the public domain. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>       /* for cos(), sin(), and sqrt() */
#include <GL/glut.h>

typedef enum {
	RESERVED, BODY_SIDE, BODY_EDGE, BODY_WHOLE, ARM_SIDE, ARM_EDGE, ARM_WHOLE,
	LEG_SIDE, LEG_EDGE, LEG_WHOLE, EYE_SIDE, EYE_EDGE, EYE_WHOLE, DINOSAUR
} displayLists;

GLfloat angle = -150;   /* in degrees */
GLboolean doubleBuffer = GL_TRUE, iconic = GL_FALSE, keepAspect = GL_FALSE;
int moving, begin;
int W = 300, H = 300;
GLdouble bodyWidth = 2.0;
int newModel = 1;
/* *INDENT-OFF* */
GLfloat body[][2] = { {0, 3}, {1, 1}, {5, 1}, {8, 4}, {10, 4}, {11, 5},
  {11, 11.5}, {13, 12}, {13, 13}, {10, 13.5}, {13, 14}, {13, 15}, {11, 16},
  {8, 16}, {7, 15}, {7, 13}, {8, 12}, {7, 11}, {6, 6}, {4, 3}, {3, 2},
  {1, 2} };
GLfloat arm[][2] = { {8, 10}, {9, 9}, {10, 9}, {13, 8}, {14, 9}, {16, 9},
  {15, 9.5}, {16, 10}, {15, 10}, {15.5, 11}, {14.5, 10}, {14, 11}, {14, 10},
  {13, 9}, {11, 11}, {9, 11} };
GLfloat leg[][2] = { {8, 6}, {8, 4}, {9, 3}, {9, 2}, {8, 1}, {8, 0.5}, {9, 0},
  {12, 0}, {10, 1}, {10, 2}, {12, 4}, {11, 6}, {10, 7}, {9, 7} };
GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15},
  {9.6, 15.25}, {9, 15.25} };
GLfloat lightZeroPosition[] = {10.0, 4.0, 10.0, 1.0};
GLfloat lightZeroColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */
GLfloat lightOnePosition[] = {-1.0, -2.0, 1.0, 0.0};
GLfloat lightOneColor[] = {0.6, 0.3, 0.2, 1.0}; /* red-tinted */
GLfloat skinColor[] = {0.1, 1.0, 0.1, 1.0}, eyeColor[] = {1.0, 0.2, 0.2, 1.0};
/* *INDENT-ON* */

void
extrudeSolidFromPolygon(GLfloat data[][2], unsigned int dataSize,
                        GLdouble thickness, GLuint side, GLuint edge, GLuint whole)
{
	static GLUtriangulatorObj *tobj = NULL;
	GLdouble vertex[3], dx, dy, len;
	int i;
	int count = dataSize / (int) (2 * sizeof(GLfloat));

	if (tobj == NULL) {
		tobj = gluNewTess();  /* create and initialize a GLU
                             polygon tesselation object */
		gluTessCallback(tobj, GLU_BEGIN, glBegin);
		gluTessCallback(tobj, GLU_VERTEX, glVertex2fv);  /* semi-tricky */
		gluTessCallback(tobj, GLU_END, glEnd);
	}
	glNewList(side, GL_COMPILE);
	glShadeModel(GL_SMOOTH);  /* smooth minimizes seeing
                               tessellation */
	gluBeginPolygon(tobj);
	for (i = 0; i < count; i++) {
		vertex[0] = data[i][0];
		vertex[1] = data[i][1];
		vertex[2] = 0;
		gluTessVertex(tobj, vertex, data[i]);
	}
	gluEndPolygon(tobj);
	glEndList();
	glNewList(edge, GL_COMPILE);
	glShadeModel(GL_FLAT);  /* flat shade keeps angular hands
                             from being "smoothed" */
	glBegin(GL_QUAD_STRIP);
	for (i = 0; i <= count; i++) {
		/* mod function handles closing the edge */
		glVertex3f(data[i % count][0], data[i % count][1], 0.0);
		glVertex3f(data[i % count][0], data[i % count][1], thickness);
		/* Calculate a unit normal by dividing by Euclidean
		   distance. We * could be lazy and use
		   glEnable(GL_NORMALIZE) so we could pass in * arbitrary
		   normals for a very slight performance hit. */
		dx = data[(i + 1) % count][1] - data[i % count][1];
		dy = data[i % count][0] - data[(i + 1) % count][0];
		len = sqrt(dx * dx + dy * dy);
		glNormal3f(dx / len, dy / len, 0.0);
	}
	glEnd();
	glEndList();
	glNewList(whole, GL_COMPILE);
	glFrontFace(GL_CW);
	glCallList(edge);
	glNormal3f(0.0, 0.0, -1.0);  /* constant normal for side */
	glCallList(side);
	glPushMatrix();
	glTranslatef(0.0, 0.0, thickness);
	glFrontFace(GL_CCW);
	glNormal3f(0.0, 0.0, 1.0);  /* opposite normal for other side */
	glCallList(side);
	glPopMatrix();
	glEndList();
}

void
makeDinosaur(void)
{
	GLfloat bodyWidth = 3.0;

	extrudeSolidFromPolygon(body, sizeof(body), bodyWidth,
	                        BODY_SIDE, BODY_EDGE, BODY_WHOLE);
	extrudeSolidFromPolygon(arm, sizeof(arm), bodyWidth / 4,
	                        ARM_SIDE, ARM_EDGE, ARM_WHOLE);
	extrudeSolidFromPolygon(leg, sizeof(leg), bodyWidth / 2,
	                        LEG_SIDE, LEG_EDGE, LEG_WHOLE);
	extrudeSolidFromPolygon(eye, sizeof(eye), bodyWidth + 0.2,
	                        EYE_SIDE, EYE_EDGE, EYE_WHOLE);
	glNewList(DINOSAUR, GL_COMPILE);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, skinColor);
	glCallList(BODY_WHOLE);
	glPushMatrix();
	glTranslatef(0.0, 0.0, bodyWidth);
	glCallList(ARM_WHOLE);
	glCallList(LEG_WHOLE);
	glTranslatef(0.0, 0.0, -bodyWidth - bodyWidth / 4);
	glCallList(ARM_WHOLE);
	glTranslatef(0.0, 0.0, -bodyWidth / 4);
	glCallList(LEG_WHOLE);
	glTranslatef(0.0, 0.0, bodyWidth / 2 - 0.1);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, eyeColor);
	glCallList(EYE_WHOLE);
	glPopMatrix();
	glEndList();
}

void
recalcModelView(void)
{
	glPopMatrix();
	glPushMatrix();
	glRotatef(angle, 0.0, 1.0, 0.0);
	glTranslatef(-8, -8, -bodyWidth / 2);
	newModel = 0;
}

void
redraw(void)
{
	if (newModel)
		recalcModelView();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glCallList(DINOSAUR);
	glutSwapBuffers();
}

/* ARGSUSED2 */
void
mouse(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		moving = 1;
		begin = x;
	}
	if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
		moving = 0;
	}
}

/* ARGSUSED1 */
void
motion(int x, int y)
{
	if (moving) {
		angle = angle + (x - begin);
		begin = x;
		newModel = 1;
		glutPostRedisplay();
	}
}

GLboolean lightZeroSwitch = GL_TRUE, lightOneSwitch = GL_TRUE;

void
controlLights(int value)
{
	switch (value) {
	case 1:
		lightZeroSwitch = !lightZeroSwitch;
		if (lightZeroSwitch) {
			glEnable(GL_LIGHT0);
		} else {
			glDisable(GL_LIGHT0);
		}
		break;
	case 2:
		lightOneSwitch = !lightOneSwitch;
		if (lightOneSwitch) {
			glEnable(GL_LIGHT1);
		} else {
			glDisable(GL_LIGHT1);
		}
		break;
	}
	glutPostRedisplay();
}

int
main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("glutdino");
	glutDisplayFunc(redraw);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutCreateMenu(controlLights);
	glutAddMenuEntry("Toggle right light", 1);
	glutAddMenuEntry("Toggle left light", 2);
	glutAttachMenu(GLUT_RIGHT_BUTTON);
	makeDinosaur();
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glMatrixMode(GL_PROJECTION);
	gluPerspective( /* field of view in degree */ 40.0,
	        /* aspect ratio */ 1.0,
	        /* Z near */ 1.0, /* Z far */ 40.0);
	glMatrixMode(GL_MODELVIEW);
	gluLookAt(0.0, 0.0, 30.0,  /* eye is at (0,0,30) */
	          0.0, 0.0, 0.0,      /* center is at (0,0,0) */
	          0.0, 1.0, 0.);      /* up is in postivie Y direction */
	glPushMatrix();       /* dummy push so we can pop on model
                           recalc */
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
	glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05);
	glLightfv(GL_LIGHT1, GL_POSITION, lightOnePosition);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, lightOneColor);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glutMainLoop();
	return 0;             /* ANSI C requires main to return int. */
}
